package vcp.server.requesthandler;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import ocsf.server.ConnectionToClient;
import vcp.client.forms.PeriodicForm;
import vcp.client.request.RetrieveStatisticsRequest;
import vcp.client.request.response.ResponseRequest;
import vcp.client.statistics.ActivityStatistics;
import vcp.client.statistics.PerformanceStatistics;
import vcp.client.statistics.StatisticsAbstract;
import vcp.entity.DayStatistics;
import vcp.entity.Subscription;
import vcp.enums.SiteEnum;
import vcp.server.DAO.DayStatisticsDAO;
import vcp.server.DAO.SubscriptionDAO;
import vcp.server.interfaces.IServer;

/**
 * 
 * @author VCP team
 *         <p>
 *         responsible for handling CEO requests for statistics analyzing
 *         </p>
 */
public class RetrieveStatisticsRequestHandler extends RequestHandler <RetrieveStatisticsRequest>
{
	public RetrieveStatisticsRequestHandler ( IServer server )
	{
		super(server);
	}

	@Override
	public void execute ( RetrieveStatisticsRequest request, ConnectionToClient client )
	{
		StatisticsAbstract statisticsReport = null;
		switch ( request.getForm().getReportType() )
		{
			case ACTIVITY:
				statisticsReport = this.retrieveActivityStatistics(request.getForm());
				break;
			case EXCEPTIONS:
				break;
			case PERFORMANCE:
				statisticsReport = this.retrievePerformanceStatistics();
				break;
			default:
				break;
		}

		ResponseRequest response = new ResponseRequest(request.getRequestID(), statisticsReport);
		this.sendResponse(response, client);
	}

	/**
	 * 
	 * @param form
	 * @return PerformanceStatistics report
	 */
	@SuppressWarnings ( "incomplete-switch" )
	private StatisticsAbstract retrievePerformanceStatistics ()
	{
		SubscriptionDAO subscriptionDAO = new SubscriptionDAO();

		PerformanceStatistics report = new PerformanceStatistics();

		int orderAhead = 0, casual = 0, businessCasual = 0, full = 0, businessFull = 0;

		List <Subscription> allSubscriptions = subscriptionDAO.findAllSubscriptions();
		for ( Subscription subscription : allSubscriptions )
		{
			switch ( subscription.getSubscriptionType() )
			{
				case CASUAL_SUBSCRIBER:
					if ( subscription.getCars().size() > 1 )
					{
						businessCasual ++ ;
					}
					else
					{
						casual ++ ;
					}
					break;
				case FULL_SUBSCRIBER:
					if ( subscription.getCars().size() > 1 )
					{
						businessFull ++ ;
					}
					else
					{
						full ++ ;
					}
					break;
				case ORDER_AHEAD:
					orderAhead ++ ;
					break;
			}
		}

		report.add(orderAhead, casual, businessCasual, full, businessFull);
		return report;
	}

	/**
	 * 
	 * @param form
	 * @return ActivityStatistics report
	 */
	private StatisticsAbstract retrieveActivityStatistics ( PeriodicForm form )
	{
		DayStatisticsDAO dayStatisticsDAO = new DayStatisticsDAO();

		ActivityStatistics activityStatistics = new ActivityStatistics();
		List <DayStatistics> dayStatistics = null;

		ArrayList <DayStatistics> totalDayStatistics = new ArrayList <>();

		for ( SiteEnum siteEnum : SiteEnum.values() )
		{
			dayStatistics = dayStatisticsDAO.findAllFromSiteByTimePeriod(form.getStartDate(), form.getEndDate(), siteEnum);
			int numOfUsed = this.calculateUsed(dayStatistics);
			int numOfCanceled = this.calculateCanceled(dayStatistics);

			activityStatistics.add(numOfUsed, numOfCanceled, siteEnum.toString());

			totalDayStatistics.addAll(dayStatistics);
		}

		int medianUsed = this.calculateMedianUsed(totalDayStatistics);
		double deviationUsed = this.calculateDeviationUsed(totalDayStatistics);
		int medianCanceled = this.calculateMedianCanceled(totalDayStatistics);
		double deviationCanceled = this.calculateDeviationCanceled(totalDayStatistics);

		activityStatistics.addParams(medianUsed, deviationUsed, medianCanceled, deviationCanceled);
		return activityStatistics;
	}

	/**
	 * 
	 * @param totalDayStatistics
	 * @return with calculated deviation of canceled
	 */
	private double calculateDeviationCanceled ( ArrayList <DayStatistics> totalDayStatistics )
	{
		float avg = 0;
		double deviation = 0, sum = 0;

		for ( DayStatistics dayStatistic : totalDayStatistics )
		{
			avg += dayStatistic.getNumOfCanceledOrders();
		}

		avg /= totalDayStatistics.size();

		for ( DayStatistics dayStatistic : totalDayStatistics )
		{
			sum += Math.pow(( dayStatistic.getNumOfCanceledOrders() - avg ), 2);
		}

		deviation = sum / totalDayStatistics.size();
		deviation = Math.sqrt(deviation);

		return deviation;
	}

	/**
	 * 
	 * @param totalDayStatistics
	 * @return with calculated median of canceled
	 */
	private int calculateMedianCanceled ( ArrayList <DayStatistics> totalDayStatistics )
	{
		Collections.sort(totalDayStatistics, DayStatistics.createCanceledCompartor());

		int median = totalDayStatistics.size() / 2;
		if ( ( totalDayStatistics.size() % 2 ) == 0 )
		{
			int middleIndex = totalDayStatistics.size() / 2;
			int middleElement = totalDayStatistics.get(middleIndex).getNumOfCanceledOrders();
			int middleElement_plus_1 = totalDayStatistics.get(middleIndex + 1).getNumOfCanceledOrders();

			median = ( middleElement + middleElement_plus_1 ) / 2;
		}

		return median;
	}

	/**
	 * 
	 * @param totalDayStatistics
	 * @return with calculated deviation of used
	 */
	private double calculateDeviationUsed ( ArrayList <DayStatistics> totalDayStatistics )
	{
		float avg = 0;
		double deviation = 0, sum = 0;

		for ( DayStatistics dayStatistic : totalDayStatistics )
		{
			avg += dayStatistic.getNumOfUsedOrders();
		}

		avg /= totalDayStatistics.size();

		for ( DayStatistics dayStatistic : totalDayStatistics )
		{
			sum += Math.pow(( dayStatistic.getNumOfUsedOrders() - avg ), 2);
		}

		deviation = sum / totalDayStatistics.size();
		deviation = Math.sqrt(deviation);

		return deviation;
	}

	/**
	 * 
	 * @param totalDayStatistics
	 * @return with calculated median of used
	 */
	private int calculateMedianUsed ( ArrayList <DayStatistics> totalDayStatistics )
	{
		Collections.sort(totalDayStatistics, DayStatistics.createUsedCompartor());

		int median = totalDayStatistics.size() / 2;
		if ( ( totalDayStatistics.size() % 2 ) == 0 )
		{
			int middleIndex = totalDayStatistics.size() / 2;
			int middleElement = totalDayStatistics.get(middleIndex).getNumOfUsedOrders();
			int middleElement_plus_1 = totalDayStatistics.get(middleIndex + 1).getNumOfUsedOrders();

			median = ( middleElement + middleElement_plus_1 ) / 2;
		}

		return median;
	}

	/**
	 * 
	 * @param dayStatistics
	 * @return calculate total of canceled
	 */
	private int calculateCanceled ( List <DayStatistics> dayStatistics )
	{
		int numCanceled = 0;
		for ( DayStatistics dayStatistic : dayStatistics )
		{
			numCanceled += dayStatistic.getNumOfCanceledOrders();
		}

		return numCanceled;
	}

	/**
	 * 
	 * @param dayStatistics
	 * @return calculate total of used
	 */
	private int calculateUsed ( List <DayStatistics> dayStatistics )
	{
		int numUsed = 0;
		for ( DayStatistics dayStatistic : dayStatistics )
		{
			numUsed += dayStatistic.getNumOfUsedOrders();
		}

		return numUsed;
	}

}
